Implement build-dependencies
authorAlex Crichton <alex@alexcrichton.com>
Sat, 1 Nov 2014 01:39:39 +0000 (18:39 -0700)
committerAlex Crichton <alex@alexcrichton.com>
Wed, 5 Nov 2014 19:37:34 +0000 (11:37 -0800)
This adds a flavor of Dependency for build dependencies. Build dependencies are
only ever used when building build commands themselves.

src/cargo/core/dependency.rs
src/cargo/ops/cargo_rustc/context.rs
src/cargo/ops/cargo_rustc/custom_build.rs
src/cargo/ops/cargo_rustc/job_queue.rs
src/cargo/ops/cargo_rustc/mod.rs
src/cargo/util/toml.rs
tests/resolve.rs
tests/test_cargo_compile_custom_build.rs

index fe83cf7160c5e67ab301e4d7119c63b44a063f8b..eefe72102f6f92a0aeb3880551e8154641f32150 100644 (file)
@@ -10,7 +10,7 @@ pub struct Dependency {
     source_id: SourceId,
     req: VersionReq,
     specified_req: Option<String>,
-    transitive: bool,
+    kind: Kind,
     only_match_name: bool,
 
     optional: bool,
@@ -22,6 +22,13 @@ pub struct Dependency {
     only_for_platform: Option<String>,
 }
 
+#[deriving(PartialEq, Clone, Show)]
+pub enum Kind {
+    Normal,
+    Development,
+    Build,
+}
+
 impl Dependency {
     /// Attempt to create a `Dependency` from an entry in the manifest.
     ///
@@ -55,7 +62,7 @@ impl Dependency {
             name: name.to_string(),
             source_id: source_id.clone(),
             req: VersionReq::any(),
-            transitive: true,
+            kind: Normal,
             only_match_name: true,
             optional: false,
             features: Vec::new(),
@@ -83,8 +90,8 @@ impl Dependency {
         &self.source_id
     }
 
-    pub fn transitive(mut self, transitive: bool) -> Dependency {
-        self.transitive = transitive;
+    pub fn kind(mut self, kind: Kind) -> Dependency {
+        self.kind = kind;
         self
     }
 
@@ -132,7 +139,15 @@ impl Dependency {
     }
 
     /// Returns false if the dependency is only used to build the local package.
-    pub fn is_transitive(&self) -> bool { self.transitive }
+    pub fn is_transitive(&self) -> bool {
+        match self.kind {
+            Normal | Build => true,
+            Development => false,
+        }
+    }
+    pub fn is_build(&self) -> bool {
+        match self.kind { Build => true, _ => false }
+    }
     pub fn is_optional(&self) -> bool { self.optional }
     /// Returns true if the default features of the dependency are requested.
     pub fn uses_default_features(&self) -> bool { self.default_features }
index 146cdb36af7496c6cf72da2e4b0b19a22830ef4e..f6ca433fadca8cd16976548b1dbda8cdaabb9d7e 100644 (file)
@@ -155,7 +155,7 @@ impl<'a, 'b: 'a> Context<'a, 'b> {
             Vacant(entry) => { entry.set(req); }
         };
 
-        for &(pkg, dep) in self.dep_targets(pkg).iter() {
+        for &(pkg, dep) in self.dep_targets(pkg, target).iter() {
             self.build_requirements(pkg, dep, req, visiting);
         }
 
@@ -229,25 +229,26 @@ impl<'a, 'b: 'a> Context<'a, 'b> {
 
     /// For a package, return all targets which are registered as dependencies
     /// for that package.
-    pub fn dep_targets(&self, pkg: &Package) -> Vec<(&'a Package, &'a Target)> {
+    pub fn dep_targets(&self, pkg: &Package, target: &Target)
+                       -> Vec<(&'a Package, &'a Target)> {
         let deps = match self.resolve.deps(pkg.get_package_id()) {
             None => return vec!(),
             Some(deps) => deps,
         };
-        deps.map(|pkg_id| self.get_package(pkg_id)).filter_map(|pkg| {
+        deps.map(|id| self.get_package(id)).filter(|dep| {
+            // If this target is a build command, then we only want build
+            // dependencies, otherwise we want everything *other than* build
+            // dependencies.
+            let pkg_dep = pkg.get_dependencies().iter().find(|d| {
+                d.get_name() == dep.get_name()
+            }).unwrap();
+            target.get_profile().is_custom_build() == pkg_dep.is_build()
+        }).filter_map(|pkg| {
             pkg.get_targets().iter().find(|&t| self.is_relevant_target(t))
                .map(|t| (pkg, t))
         }).collect()
     }
 
-    /// For a package, return all targets which are registered as build
-    /// dependencies for that package.
-    pub fn build_dep_targets(&self, _pkg: &Package)
-                             -> Vec<(&'a Package, &'a Target)> {
-        // FIXME: needs implementation
-        vec![]
-    }
-
     /// Gets a package for the given package id.
     pub fn get_package(&self, id: &PackageId) -> &'a Package {
         self.package_set.iter()
index 5f161dc195de10c8efddf9ff0e18c59f8d95e655..b46505f1212a14e12484ce30907a524162b94b6e 100644 (file)
@@ -40,7 +40,7 @@ pub fn prepare(pkg: &Package, target: &Target, cx: &mut Context)
     // Start preparing the process to execute, starting out with some
     // environment variables.
     let profile = target.get_profile();
-    let mut p = super::process(to_exec, pkg, cx)
+    let mut p = super::process(to_exec, pkg, target, cx)
                      .env("OUT_DIR", Some(&build_output))
                      .env("CARGO_MANIFEST_DIR", Some(pkg.get_manifest_path()
                                                         .dir_path()
@@ -70,7 +70,10 @@ pub fn prepare(pkg: &Package, target: &Target, cx: &mut Context)
     // This information will be used at build-time later on to figure out which
     // sorts of variables need to be discovered at that time.
     let lib_deps = {
-        cx.dep_targets(pkg).iter().filter_map(|&(pkg, _)| {
+        let non_build_target = pkg.get_targets().iter().find(|t| {
+            !t.get_profile().is_custom_build()
+        }).unwrap();
+        cx.dep_targets(pkg, non_build_target).iter().filter_map(|&(pkg, _)| {
             pkg.get_manifest().get_links()
         }).map(|s| s.to_string()).collect::<Vec<_>>()
     };
index e11bf0da7d7374f805f402f95bf9e729681f231a..86d8b9982beaa117e0478841d1058aff84c68225 100644 (file)
@@ -250,9 +250,14 @@ impl<'a> Dependency<(&'a Resolve, &'a PackageSet)>
         match stage {
             StageStart => Vec::new(),
 
+            // Building the build command itself starts off pretty easily,we
+            // just need to depend on all of the library stages of our own build
+            // dependencies (making them available to us).
             StageBuildCustomBuild => {
-                // FIXME: build dependencies should come into play here
-                vec![(id, StageStart)]
+                let mut base = vec![(id, StageStart)];
+                base.extend(deps.filter(|&(_, dep)| dep.is_build())
+                                .map(|(id, _)| (id, StageLibraries)));
+                base
             }
 
             // When running a custom build command, we need to be sure that our
index f6aae0cad46caf19af0f5afb15ca148e98992402..dc9798e5cf173fb03098a92ce50f5dd461180006 100644 (file)
@@ -264,6 +264,9 @@ fn compile_custom_old(pkg: &Package, cmd: &str,
         Some(profile) => profile,
         None => return Err(internal(format!("no profile for {}", cx.env())))
     };
+    // Just need a target which isn't a custom build command
+    let target = &pkg.get_targets()[0];
+    assert!(!target.get_profile().is_custom_build());
 
     // TODO: this needs to be smarter about splitting
     let mut cmd = cmd.split(' ');
@@ -272,7 +275,7 @@ fn compile_custom_old(pkg: &Package, cmd: &str,
     let layout = cx.layout(pkg, KindTarget);
     let output = layout.native(pkg);
     let old_output = layout.proxy().old_native(pkg);
-    let mut p = try!(process(cmd.next().unwrap(), pkg, cx))
+    let mut p = try!(process(cmd.next().unwrap(), pkg, target, cx))
                      .env("OUT_DIR", Some(&output))
                      .env("DEPS_DIR", Some(&output))
                      .env("TARGET", Some(cx.target_triple()))
@@ -294,7 +297,7 @@ fn compile_custom_old(pkg: &Package, cmd: &str,
     }
 
 
-    for &(pkg, _) in cx.dep_targets(pkg).iter() {
+    for &(pkg, _) in cx.dep_targets(pkg, target).iter() {
         let name: String = pkg.get_name().chars().map(|c| {
             match c {
                 '-' => '_',
@@ -389,7 +392,7 @@ fn rustc(package: &Package, target: &Target,
 fn prepare_rustc(package: &Package, target: &Target, crate_types: Vec<&str>,
                  cx: &Context, req: PlatformRequirement)
                  -> CargoResult<Vec<(ProcessBuilder, Kind)>> {
-    let base = try!(process("rustc", package, cx));
+    let base = try!(process("rustc", package, target, cx));
     let base = build_base_args(cx, base, package, target, crate_types.as_slice());
 
     let target_cmd = build_plugin_args(base.clone(), cx, package, target, KindTarget);
@@ -415,7 +418,7 @@ fn rustdoc(package: &Package, target: &Target,
     let kind = KindTarget;
     let pkg_root = package.get_root();
     let cx_root = cx.layout(package, kind).proxy().dest().join("doc");
-    let rustdoc = try!(process("rustdoc", package, cx)).cwd(pkg_root.clone());
+    let rustdoc = try!(process("rustdoc", package, target, cx)).cwd(pkg_root.clone());
     let mut rustdoc = rustdoc.arg(target.get_src_path())
                          .arg("-o").arg(cx_root)
                          .arg("--crate-name").arg(target.get_name());
@@ -584,7 +587,6 @@ fn build_deps_args(mut cmd: ProcessBuilder, target: &Target, package: &Package,
     // Traverse the entire dependency graph looking for -L paths to pass for
     // native dependencies.
     // OLD-BUILD: to-remove
-    // FIXME: traverse build deps for build cmds
     let mut dirs = Vec::new();
     each_dep(package, cx, |pkg| {
         if pkg.get_manifest().get_build().len() > 0 {
@@ -595,25 +597,17 @@ fn build_deps_args(mut cmd: ProcessBuilder, target: &Target, package: &Package,
         cmd = cmd.arg("-L").arg(dir);
     }
 
-    if target.get_profile().is_custom_build() {
-        // Custom build commands don't link to any other targets in the package,
-        // and they also link to all build dependencies, not normal dependencies
-        for &(pkg, target) in cx.build_dep_targets(package).iter() {
-            cmd = try!(link_to(cmd, pkg, target, cx, kind));
-        }
-    } else {
-        for &(pkg, target) in cx.dep_targets(package).iter() {
-            cmd = try!(link_to(cmd, pkg, target, cx, kind));
-        }
+    for &(pkg, target) in cx.dep_targets(package, target).iter() {
+        cmd = try!(link_to(cmd, pkg, target, cx, kind));
+    }
 
-        let targets = package.get_targets().iter().filter(|target| {
-            target.is_lib() && target.get_profile().is_compile()
-        });
+    let targets = package.get_targets().iter().filter(|target| {
+        target.is_lib() && target.get_profile().is_compile()
+    });
 
-        if target.is_bin() {
-            for target in targets.filter(|f| !f.is_staticlib()) {
-                cmd = try!(link_to(cmd, package, target, cx, kind));
-            }
+    if target.is_bin() && !target.get_profile().is_custom_build() {
+        for target in targets.filter(|f| !f.is_staticlib()) {
+            cmd = try!(link_to(cmd, package, target, cx, kind));
         }
     }
 
@@ -643,7 +637,7 @@ fn build_deps_args(mut cmd: ProcessBuilder, target: &Target, package: &Package,
     }
 }
 
-pub fn process<T: ToCStr>(cmd: T, pkg: &Package,
+pub fn process<T: ToCStr>(cmd: T, pkg: &Package, target: &Target,
                           cx: &Context) -> CargoResult<ProcessBuilder> {
     // When invoking a tool, we need the *host* deps directory in the dynamic
     // library search path for plugins and such which have dynamic dependencies.
@@ -655,7 +649,7 @@ pub fn process<T: ToCStr>(cmd: T, pkg: &Package,
     // Also be sure to pick up any native build directories required by plugins
     // or their dependencies
     let mut native_search_paths = HashSet::new();
-    for &(dep, target) in cx.dep_targets(pkg).iter() {
+    for &(dep, target) in cx.dep_targets(pkg, target).iter() {
         if !target.get_profile().is_for_host() { continue }
         each_dep(dep, cx, |dep| {
             if dep.get_manifest().get_build().len() > 0 {
index a292d6a2e3d15c152b4a062d151963288e89b70a..f2abb2dd1f45a5b5d53e949c702c70b3a9535a5c 100644 (file)
@@ -10,8 +10,9 @@ use semver;
 use serialize::{Decodable, Decoder};
 
 use core::SourceId;
-use core::manifest::{LibKind, Lib, Dylib, Profile, ManifestMetadata};
 use core::{Summary, Manifest, Target, Dependency, PackageId};
+use core::dependency::{Build, Development};
+use core::manifest::{LibKind, Lib, Dylib, Profile, ManifestMetadata};
 use core::package_id::Metadata;
 use util::{CargoResult, Require, human, ToUrl, ToSemver};
 
@@ -210,6 +211,7 @@ pub struct TomlManifest {
     bench: Option<Vec<TomlTestTarget>>,
     dependencies: Option<HashMap<String, TomlDependency>>,
     dev_dependencies: Option<HashMap<String, TomlDependency>>,
+    build_dependencies: Option<HashMap<String, TomlDependency>>,
     features: Option<HashMap<String, Vec<String>>>,
     target: Option<HashMap<String, TomlPlatform>>,
 }
@@ -481,13 +483,20 @@ impl TomlManifest {
             };
 
             // Collect the deps
-            try!(process_dependencies(&mut cx, false, None, self.dependencies.as_ref()));
-            try!(process_dependencies(&mut cx, true, None, self.dev_dependencies.as_ref()));
+            try!(process_dependencies(&mut cx, self.dependencies.as_ref(),
+                                      |dep| dep));
+            try!(process_dependencies(&mut cx, self.dev_dependencies.as_ref(),
+                                      |dep| dep.kind(Development)));
+            try!(process_dependencies(&mut cx, self.build_dependencies.as_ref(),
+                                      |dep| dep.kind(Build)));
 
             if let Some(targets) = self.target.as_ref() {
                 for (name, platform) in targets.iter() {
-                    try!(process_dependencies(&mut cx, false, Some(name.clone()),
-                                              platform.dependencies.as_ref()));
+                    try!(process_dependencies(&mut cx,
+                                              platform.dependencies.as_ref(),
+                                              |dep| {
+                        dep.only_for_platform(Some(name.clone()))
+                    }));
                 }
             }
         }
@@ -528,8 +537,9 @@ impl TomlManifest {
     }
 }
 
-fn process_dependencies<'a>(cx: &mut Context<'a>, dev: bool, platform: Option<String>,
-                            new_deps: Option<&HashMap<String, TomlDependency>>)
+fn process_dependencies<'a>(cx: &mut Context<'a>,
+                            new_deps: Option<&HashMap<String, TomlDependency>>,
+                            f: |Dependency| -> Dependency)
                             -> CargoResult<()> {
     let dependencies = match new_deps {
         Some(ref dependencies) => dependencies,
@@ -568,8 +578,7 @@ fn process_dependencies<'a>(cx: &mut Context<'a>, dev: bool, platform: Option<St
                                          details.version.as_ref()
                                                 .map(|v| v.as_slice()),
                                          &new_source_id));
-        let dep = dep.transitive(!dev)
-                     .only_for_platform(platform.clone())
+        let dep = f(dep)
                      .features(details.features.unwrap_or(Vec::new()))
                      .default_features(details.default_features.unwrap_or(true))
                      .optional(details.optional.unwrap_or(false));
index 44fae3425be5ec734b75a283acc5c121508e65fe..1c9b270961a7206ba556e76038e62655f509ef53 100644 (file)
@@ -8,6 +8,7 @@ use std::collections::HashMap;
 use hamcrest::{assert_that, equal_to, contains};
 
 use cargo::core::source::SourceId;
+use cargo::core::dependency::Development;
 use cargo::core::{Dependency, PackageId, Summary, Registry};
 use cargo::util::{CargoResult, ToUrl};
 use cargo::core::resolver::{mod, ResolveEverything};
@@ -191,14 +192,14 @@ fn test_resolving_with_same_name() {
 #[test]
 fn test_resolving_with_dev_deps() {
     let mut reg = registry(vec!(
-        pkg!("foo" => ["bar", dep("baz").transitive(false)]),
-        pkg!("baz" => ["bat", dep("bam").transitive(false)]),
+        pkg!("foo" => ["bar", dep("baz").kind(Development)]),
+        pkg!("baz" => ["bat", dep("bam").kind(Development)]),
         pkg!("bar"),
         pkg!("bat")
     ));
 
     let res = resolve(pkg_id("root"),
-                      vec![dep("foo"), dep("baz").transitive(false)],
+                      vec![dep("foo"), dep("baz").kind(Development)],
                       &mut reg).unwrap();
 
     assert_that(&res, contains(names(["root", "foo", "bar", "baz"])));
index d94861df262e8ba9b86ef917faa4f86364db7859..9eac0c63f9da29f4057315a219dd502c166fba65 100644 (file)
@@ -540,3 +540,78 @@ test!(propagation_of_l_flags {
 {running} `rustc [..] --crate-name foo [..] -L bar[..]-L foo[..]`
 ", compiling = COMPILING, running = RUNNING).as_slice()));
 })
+
+test!(build_deps_simple {
+    let p = project("foo")
+        .file("Cargo.toml", r#"
+            [project]
+            name = "foo"
+            version = "0.5.0"
+            authors = []
+            build = "build.rs"
+            [build-dependencies.a]
+            path = "a"
+        "#)
+        .file("src/lib.rs", "")
+        .file("build.rs", "
+            extern crate a;
+            fn main() {}
+        ")
+        .file("a/Cargo.toml", r#"
+            [project]
+            name = "a"
+            version = "0.5.0"
+            authors = []
+        "#)
+        .file("a/src/lib.rs", "");
+
+    assert_that(p.cargo_process("build").arg("-v"),
+                execs().with_status(0)
+                       .with_stdout(format!("\
+{compiling} a v0.5.0 (file://[..])
+{running} `rustc [..] --crate-name a [..]`
+{compiling} foo v0.5.0 (file://[..])
+{running} `rustc build.rs [..] --extern a=[..]`
+{running} `[..]foo-[..]build-script-build`
+{running} `rustc [..] --crate-name foo [..]`
+", compiling = COMPILING, running = RUNNING).as_slice()));
+})
+
+test!(build_deps_not_for_normal {
+    let (_, target) = ::cargo::ops::rustc_version().unwrap();
+    let p = project("foo")
+        .file("Cargo.toml", r#"
+            [project]
+            name = "foo"
+            version = "0.5.0"
+            authors = []
+            build = "build.rs"
+            [build-dependencies.a]
+            path = "a"
+        "#)
+        .file("src/lib.rs", "extern crate a;")
+        .file("build.rs", "
+            extern crate a;
+            fn main() {}
+        ")
+        .file("a/Cargo.toml", r#"
+            [project]
+            name = "a"
+            version = "0.5.0"
+            authors = []
+        "#)
+        .file("a/src/lib.rs", "");
+
+    assert_that(p.cargo_process("build").arg("-v").arg("--target").arg(target),
+                execs().with_status(101)
+                       .with_stderr("\
+[..]lib.rs[..] error: can't find crate for `a`
+[..]lib.rs[..] extern crate a;
+[..]           ^~~~~~~~~~~~~~~
+error: aborting due to previous error
+Could not compile `foo`.
+
+Caused by:
+  Process didn't exit successfully: [..]
+"));
+})